package com.riguz.forks.framework.route.impl;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.riguz.forks.framework.Framework;
import com.riguz.forks.framework.FrameworkException;
import com.riguz.forks.framework.mvc.HttpMethod;
import com.riguz.forks.framework.mvc.RequestHandler;
import com.riguz.forks.framework.route.Route;
import com.riguz.forks.framework.route.RouteLoader;
import com.riguz.forks.framework.util.ServletUtil;
import com.riguz.gags.base.Classes;
import com.riguz.gags.base.Strings;
import com.riguz.gags.io.Config;
import com.riguz.gags.tuple.Pair;

public class DefaultRouteLoader implements RouteLoader {
    private static final Logger logger = LoggerFactory.getLogger(DefaultRouteLoader.class);

    @SuppressWarnings("unchecked")
    @Override
    public Route load() {
        Route route = Framework.getRoute();
        Map<String, Object> config = Config.use(Framework.ROUTE_SETTINGS);;
        if (config == null) return null;
        List<String> defs = (List<String>) config.get("mapping");
        List<String> packages = (List<String>) config.get("packages");
        if (defs == null) return null;
        for (String def : defs) {
            String[] arr = def.split("\\s+");
            if (arr.length != 3) {
                logger.error("Invalid route config:" + def);
                continue;
            }
            try {
                Pair<Class<?>, Method> p = this.loadMethod(packages, arr[2]);
                RequestHandler handler = new RequestHandler(p.getLeft(), p.getRight());
                String path = this.formatPath(arr[1]);
                logger.debug("Adding route:({}) [{}] =>{}", arr[0], path, handler);
                route.addRoute(HttpMethod.valueOf(arr[0]), path, handler);
            } catch (FrameworkException e) {
                e.printStackTrace();
                return null;
            }
        }        
        return route;
    }
    
    private String formatPath(String path){
        //convert /xx/yy/:name/ => /xx/yy/?/
        return ServletUtil.formatUrl(path).replaceAll(":\\w+", "?").replaceAll("\\*\\w+", "*");
    }

    private Pair<Class<?>, Method> loadMethod(List<String> namespace, String methodDef) throws FrameworkException {
        if (!this.checkMethodDef(methodDef)) throw new FrameworkException("Check route def failed." + methodDef);
        int firstDot = methodDef.indexOf('.'), lastDot = methodDef.lastIndexOf('.');

        // find class
        Class<?> controllerClass = null;
        String className = methodDef.substring(0, lastDot);
        if (firstDot != lastDot) controllerClass = Classes.loadClass(className);
        else {
            for (String n : namespace) {
                controllerClass = Classes.loadClass(n + "." + className);
                if (controllerClass != null) break;
            }
        }
        if (controllerClass == null) throw new FrameworkException("Failed to load controller class:" + methodDef);

        // find method
        int leftBracket = methodDef.indexOf('('), rightBracket = methodDef.indexOf(')');
        String methodName = methodDef.substring(lastDot + 1, leftBracket);
        String methodParams = methodDef.substring(leftBracket + 1, rightBracket).trim();
        Class<?>[] paramClasses = null;
        if (!Strings.isNullOrEmpty(methodParams)) {
            String paramsArr[] = methodParams.split(",");
            paramClasses = new Class<?>[paramsArr.length];
            for (int l = 0; l < paramsArr.length; l++) {
                String p = paramsArr[l].trim();
                String name = p.split(":")[0], type = p.split(":")[1];
                if (!type.contains(".")) {
                    paramClasses[l] = this.getBaseType(type);
                    if (paramClasses[l] == null) throw new FrameworkException("Param class not found:" + type);
                } else {
                    try {
                        paramClasses[l] = Class.forName(type);
                    } catch (ClassNotFoundException e) {
                        throw new FrameworkException("Param class not found:" + type);
                    }
                }
            }
        }
        Method m = Classes.getMethod(controllerClass, methodName, paramClasses);
        if (m == null) throw new FrameworkException("Method not found:" + className + "." + methodName);
        return new Pair<Class<?>, Method>(controllerClass, m);
    }

    private Class<?> getBaseType(String baseType) {
        if ("String".equals(baseType)) return String.class;
        if ("Integer".equals(baseType)) return Integer.class;
        if ("Long".equals(baseType)) return Long.class;
        if ("Float".equals(baseType)) return Float.class;
        if ("Double".equals(baseType)) return Double.class;
        if ("Date".equals(baseType)) return Date.class;
        if ("Boolean".equals(baseType)) return Boolean.class;
        if ("int".equals(baseType)) return int.class;
        if ("long".equals(baseType)) return long.class;
        if ("float".equals(baseType)) return float.class;
        if ("double".equals(baseType)) return double.class;
        if ("boolean".equals(baseType)) return boolean.class;
        return null;
    }

    // (\w+\.)*\w+\(\s*(\w+\s*:\s*\w+)*\s*(,\s*\w+\s*\:\s*(\w+\.)*\w+)*\)
    // (\\w+\\.)*\\w+\\(\\s*(\\w+\\s*:\\s*\\w+)*\\s*(,\\s*\\w+\\s*\\:\\s*(\\w+\\.)*\\w+)*\\)
    private Pattern methodPattern = Pattern.compile("(\\w+\\.)*\\w+\\(\\s*(\\w+\\s*:\\s*\\w+)*\\s*(,\\s*\\w+\\s*\\:\\s*(\\w+\\.)*\\w+)*\\)");

    private boolean checkMethodDef(String def) {
        return methodPattern.matcher(def).matches();
    }

}
